package xyz.scootaloo.kami.cloud.service.impl

import io.vertx.core.Future
import io.vertx.core.Future.succeededFuture
import io.vertx.core.json.JsonObject
import io.vertx.ext.web.impl.LRUCache
import xyz.scootaloo.kami.cloud.lang.*
import xyz.scootaloo.kami.cloud.model.EntityUser
import xyz.scootaloo.kami.cloud.model.core.AccessLevel
import xyz.scootaloo.kami.cloud.model.core.SC
import xyz.scootaloo.kami.cloud.model.core.StatusCode
import xyz.scootaloo.kami.cloud.model.dao.UserDAO
import xyz.scootaloo.kami.cloud.model.po.AccountForm
import xyz.scootaloo.kami.cloud.model.po.AccountFormBlock
import xyz.scootaloo.kami.cloud.model.po.AccountPlaintextFormBlock
import xyz.scootaloo.kami.cloud.model.po.ServiceResult
import xyz.scootaloo.kami.cloud.service.AccountService
import xyz.scootaloo.kami.cloud.service.ServiceLifeCycle
import xyz.scootaloo.kami.cloud.service.TokenService
import xyz.scootaloo.kami.cloud.service.impl.AccountServiceImpl.UserManager.CACHE_CAPACITY
import xyz.scootaloo.kami.cloud.util.JwtHelper
import xyz.scootaloo.kami.cloud.util.Placeholder
import xyz.scootaloo.kami.cloud.util.wrapFut

/**
 * @author flutterdash@qq.com
 * @since 2022/4/12 10:08
 */
object AccountServiceImpl : AccountService, ServiceLifeCycle {

    private val log by lazy { getLogger("account-service") }

    override fun onDatabaseAvailable() {
        UserManager.initCache()
    }

    override fun register(ipAddress: String, form: AccountForm): Future<ServiceResult<String>> {
        return Bus.callService(Constant.EB_ACCOUNT_REGISTER, serializerProcess(ipAddress, form))
    }

    override fun login(ipAddress: String, form: AccountForm): Future<ServiceResult<String>> {
        return Bus.callService(Constant.EB_ACCOUNT_LOGIN, serializerProcess(ipAddress, form))
    }

    override fun login(ipAddress: String, username: String, password: String): Future<ServiceResult<Unit>> {
        val form = AccountPlaintextFormBlock(ipAddress, username, password)
        return Bus.callService(Constant.EB_ACCOUNT_P_LOGIN, serializer(form))
    }

    override fun logout(): Future<ServiceResult<Unit>> {
        TODO("Not yet implemented")
    }

    override fun findUser(username: String): Future<EntityUser?> {
        return Bus.request<EntityUser>(Constant.EB_ACCOUNT_FD_USER, username)
    }

    override fun internal(): AccountService.InternalAccountServiceApi {
        return InternalAccountServiceApiImpl
    }

    private fun serializerProcess(ipAddress: String, form: AccountForm): JsonObject {
        return serializer(AccountFormBlock(ipAddress, form))
    }

    // implements

    private val tokenService = TokenService().internal()

    object InternalAccountServiceApiImpl : AccountService.InternalAccountServiceApi {
        override fun register(ipAddress: String, form: AccountForm): Future<ServiceResult<String>> {
            return UserManager.findByName(form.username).compose { dbUser ->
                if (dbUser != null) {
                    usernameDuplicateErr(form.username).wrapFut()
                } else {
                    registerForNewUser(ipAddress, form)
                }
            }
        }

        override fun login(ipAddress: String, form: AccountForm): Future<ServiceResult<String>> {
            // decode password
            val decodedRsl = tokenService.decodeContent(ipAddress, form.clientId, form.password)
            if (!decodedRsl.succeeded()) return succeededFuture(decodedRsl)
            val plaintextPassword = decodedRsl.result!!

            return plaintextLogin(form.username, plaintextPassword).compose {
                if (!it.succeeded()) {
                    ServiceResult.error<String>(it.code).wrapFut()
                } else {
                    UserManager.findByName(form.username).compose { entityUser ->
                        if (entityUser == null) {
                            usernameNotExistsErr(form.username).wrapFut()
                        } else {
                            issueJwtForUser(entityUser, SC.ACCOUNT_LOGIN_SUCCESS).wrapFut()
                        }
                    }
                }
            }
        }

        override fun login(ipAddress: String, username: String, password: String): Future<ServiceResult<Unit>> {
            return plaintextLogin(username, password)
        }

        override fun logout(): Future<ServiceResult<Unit>> {
            TODO("Not yet implemented")
        }

        override fun findUserByName(username: String): Future<EntityUser?> {
            return UserManager.findByName(username)
        }

        private fun plaintextLogin(username: String, plaintextPassword: String): Future<ServiceResult<Unit>> {
            return UserManager.findByName(username).compose {
                if (it == null) {
                    usernameNotExistsErr(username).toUnit().wrapFut()
                } else {
                    val matchResult = it.password == plaintextPassword
                    if (matchResult) {
                        ServiceResult.unit(SC.ACCOUNT_LOGIN_SUCCESS).wrapFut()
                    } else {
                        ServiceResult.unit(SC.ACCOUNT_MATCHING_FAILURE).wrapFut()
                    }
                }
            }
        }

        private fun registerForNewUser(ipAddress: String, form: AccountForm): Future<ServiceResult<String>> {
            // decode password
            val decodedRsl = tokenService.decodeContent(ipAddress, form.clientId, form.password)
            if (!decodedRsl.succeeded()) return decodedRsl.wrapFut()
            val plaintextPass = decodedRsl.result!!

            // async store entity record to database, then issue jwt for user
            return storeNewUser(form.username, plaintextPass).compose {
                issueJwtForUser(it, SC.ACCOUNT_LOGIN_SUCCESS).wrapFut()
            }
        }

        private fun storeNewUser(username: String, password: String): Future<EntityUser> {
            val entityUser = EntityUser.create(username, password, AccessLevel.USER)
            return blocking<EntityUser> {
                UserDAO.store(entityUser)
                it.complete(entityUser)
            }.submit()
        }

        private fun issueJwtForUser(user: EntityUser, code: StatusCode) = ServiceResult(
            code, JwtHelper.issueJwt(user.id, user.username, user.role)
        )

        private fun usernameNotExistsErr(username: String): ServiceResult<String> {
            val c = SC.ACCOUNT_USERNAME_NOT_EXISTS
            return ServiceResult(SC(c.code, Placeholder.splice(c.msg, username)))
        }

        private fun usernameDuplicateErr(username: String): ServiceResult<String> {
            val c = SC.USERNAME_DUPLICATE
            return ServiceResult(SC(c.code, Placeholder.splice(c.msg, username)))
        }
    }

    /**
     * 用户表缓存说明
     *
     * 系统启动, 预缓存[CACHE_CAPACITY]条用户记录;
     * 当有用户登录系统时, 先检查缓存中是否有该用户信息(根据用户名查询)
     *     1. 如果有, 则直接用缓存中的用户记录和用户提交的表单信息(表单中是加密数据)的密码进行比对(解密后)
     *         a. md5之后如果结果一致, 则将用户的明文信息报错到缓存中
     *         b. 密码错误
     *     2. 没有, 则从数据库中取数据, 保存到缓存中, 重复步骤1
     */
    object UserManager {
        private const val CACHE_CAPACITY = 50
        private val userCache: LRUCache<String, EntityUser> = LRUCache(CACHE_CAPACITY)

        fun initCache() {
            userCache.clear()
            blocking<List<EntityUser>> {
                val users = UserDAO.take(CACHE_CAPACITY)
                log.info("缓存用户记录${users.size}条")
                it.complete(users)
            }.submit().onSuccess { it.forEach(::cacheEntityUser) }
        }

        fun findByName(name: String): Future<EntityUser?> {
            return userCache[name]?.wrapFut() ?: return asyncFindUserByName(name)
        }

        private fun cacheEntityUser(user: EntityUser) {
            userCache[user.username] = user
        }

        private fun asyncFindUserByName(name: String) = blocking<EntityUser?> {
            it.complete(UserDAO.findByName(name))
        }.submit().onSuccess { it.ifNotNull(::cacheEntityUser) }
    }
}